<template>
  <el-upload
    ref="uploadRef"
    class="g-upload"
    :class="{ inline: inline, 'is-row': span > 0 }"
    :action="action"
    :headers="headers"
    :data="postParams"
    name="file"
    :on-exceed="handleExceed"
    :before-upload="handleBeforeUpload"
    :on-success="handleSuccess"
    :on-error="handleError"
    :on-remove="handleRemove"
    :on-preview="handlePreview"
    :on-change="handleChange"
    :accept="accept"
    :limit="limit"
    v-model:file-list="localFileList"
    :disabled="uploadLoading"
    :multiple="multiple"
    v-loading="previewLoading"
    v-bind="$attrs"
  >
    <slot :loading="uploadLoading" v-if="!$attrs.disabled"></slot>
    <template #tip v-if="!$attrs.disabled">
      <slot name="tip">
        <div class="el-upload__tip g-font-12n3 inline--tip is-row--tip" v-if="tip">{{ tip }}</div>
        <!-- <div class="el-upload__tip" v-if="tip">{{ tip }}</div> -->
      </slot>
    </template>
  </el-upload>
</template>
<script lang="ts">
import { defineComponent, toRefs, computed, ref } from 'vue';
import type { UploadFile } from 'element-plus/es/components/upload/src/upload';
import { isJSON, getSuffix } from '@/utils';
import { useMessage } from '@/hooks/useMessage';
import { store } from '@/store/index';
import { UserActionTypes } from '@/store/modules/user/action-types';
import { HTTP_RESPONSE_CODE } from '@/constants';
import { genFileId } from 'element-plus';
import type { UploadProps, UploadRawFile } from 'element-plus';
import { useDownload } from '@/hooks/useDownload';
import { Base64 } from 'js-base64';

export default defineComponent({
  name: 'GUpload',
  props: {
    multiple: {
      type: Boolean,
      default: false
    },
    tip: {
      type: String,
      default: ''
    },
    accept: {
      type: String,
      default: '*'
    },
    limit: {
      type: Number,
      default: 1
    },
    action: {
      type: String,
      default: '',
      required: true
    },
    fileList: {
      type: Array,
      default: () => {
        return [];
      }
    },
    postParams: {
      type: Object,
      default: () => {
        return {};
      }
    },
    // 文件大小限制，单位M
    fileSizeLimit: {
      type: Number,
      default: 20
    },
    inline: {
      // 是否文件和上传按钮在同一行,仅限单文件上传的样式
      type: Boolean,
      default: false
    },
    span: {
      // 多文件每个item占的比例  最大24
      type: Number,
      default: 0
    },
    /**
     * 接口下载文件
     */
    previewByApi: {
      type: Boolean,
      default: false
    }
  },
  emits: ['update:fileList', 'error', 'success', 'change'],
  setup(props, { emit }) {
    const { createMessage } = useMessage();
    const { accept } = toRefs(props);
    const uploadRef = ref();
    const headers = ref({});
    const uploadLoading = ref(false);
    // const fileList = ref<RawFile[]>([]);

    const localFileList = computed({
      get: () => {
        return props.fileList;
      },
      set: val => {
        emit('update:fileList', val);
      }
    });
    const needUploadList = ref([] as any[]); // 需要上传的文件总列表，用来判断文件是否上传完成
    // 超出上传限制时的操作
    const handleExceed: UploadProps['onExceed'] = files => {
      if (props.limit == 1) {
        uploadRef.value!.clearFiles();
        const file = files[0] as UploadRawFile;
        file.uid = genFileId();
        uploadRef.value!.handleStart(file);
        uploadRef.value!.submit();
      } else {
        createMessage.warning(`最多上传${props.limit}个文件`);
      }
    };

    // 上传失败 http请求失败等
    const handleError = (err: Error, file: UploadFile, fileList: UploadFile[]) => {
      console.log('handleError', err.message, file, fileList);
      needUploadList.value = needUploadList.value.filter((f: any) => f.uid !== file.uid);
      endUpload();
      if (isJSON(err.message)) {
        const errMessageJson = JSON.parse(err.message);
        if (Object.keys(errMessageJson).length && errMessageJson.msg) {
          createMessage.error(errMessageJson.msg);
        } else {
          createMessage.error(err.message);
        }
      } else {
        createMessage.error(err.message);
      }
      emit('error');
    };

    // 上传成功，仅为http上传成功，不代表文件后端校验成功
    const handleSuccess = (response: any, file: UploadFile, fileList: UploadFile[]) => {
      console.log('handleSuccess', response, file, fileList);
      const { code, data, msg } = response;
      if (props.multiple) {
        if (code !== HTTP_RESPONSE_CODE.OK_CODE) {
          createMessage.error(`${file.name}: ${msg} `);
          endUpload();
          const index = fileList.findIndex((fileItem: UploadFile) => fileItem.uid === file.uid);
          if (index > -1) {
            fileList.splice(index, 1);
          }
          return;
        }
        needUploadList.value = needUploadList.value.filter((f: any) => f.uid !== file.uid);
        if (needUploadList.value.length === 0) {
          // 所有文件都已经上传完成
          endUpload();
          const formatFileList = fileList.map((item: any) => {
            return {
              name: item.name,
              url: item?.url || item?.response?.data?.fileUrl
            };
          });
          console.log('handleSuccess-after', formatFileList);

          emit('update:fileList', formatFileList);
          emit('success', formatFileList);
        } else {
          emit('error', data);
        }
      } else {
        if (code !== HTTP_RESPONSE_CODE.OK_CODE) {
          console.log('handleSuccess error');
          const index = fileList.findIndex((fileItem: UploadFile) => fileItem.uid === file.uid);
          if (index > -1) {
            fileList.splice(index, 1);
          }
          createMessage.error(msg);
          endUpload();
          emit('error', data);
        } else {
          endUpload();
          console.log('fileList', fileList, data?.fileUrl);
          const formatFileList = fileList.map((item: any) => {
            return {
              name: item.name,
              url: item?.url || item?.response?.data?.fileUrl
            };
          });
          emit('update:fileList', formatFileList);
          emit('success', file);
        }
      }
    };
    // 上传前
    const handleBeforeUpload = (file: UploadFile) => {
      console.log('handleBeforeUpload', file);
      const { name, size } = file;

      // 文件大小校验
      if (size) {
        const isLt20M = size < props.fileSizeLimit * 1024 * 1024;
        if (!isLt20M) {
          createMessage.error(`上传的文件大小不能超过${props.fileSizeLimit}MB`);
          emit('error');
          return false;
        }
      }

      // 上传文件格式有限制
      if (accept.value !== '*' && accept.value.indexOf('.') > -1) {
        const fileSuffix = getSuffix(name);
        console.log('fileSuffix', fileSuffix);
        if (!fileSuffix) {
          createMessage.error('上传文件格式不支持');
          emit('error');
          return false;
        }
        const suffixArr = accept.value
          .toLowerCase()
          .replace(/\./g, '')
          .split(',')
          .map(item => item.trim());
        console.log('suffixArr', suffixArr);
        if (!suffixArr.includes(fileSuffix)) {
          createMessage.error('上传文件格式不支持');
          emit('error');
          return false;
        }
      }

      // 校验登录状态
      return new Promise((resolve, reject) => {
        store
          .dispatch(UserActionTypes.ACTION_GET_ACCESS_TOKEN)
          .then(token => {
            console.log('gettoken', token);
            headers.value = {
              Authorization: `Bearer ${token}`
            };
            startUpload();
            needUploadList.value.push(file);
            resolve(true);
          })
          .catch(() => {
            reject(new Error('token获取失败'));
            createMessage.error('登录状态已过期，请重新登录！');
            store.dispatch(UserActionTypes.ACTION_LOGIN_OUT);
            emit('error');
          });
      });
    };

    // 手动上传
    const handleSubmit = () => {
      uploadRef.value.submit();
    };

    // 开始上传
    const startUpload = () => {
      uploadLoading.value = true;
    };

    // 结束上传
    const endUpload = () => {
      uploadLoading.value = false;
    };

    // 添加文件后的临时备份，用于判断是否已经添加了文件
    const tempFileList = ref<UploadFile[]>([]);
    function handleChange(file: UploadFile, fileList: UploadFile[]) {
      console.log('handleChange', file, fileList);
      emit('change');
      const { status } = file || {};
      if (status === 'ready') {
        tempFileList.value.push(file);
      }
      console.log('tempFileList', tempFileList);
    }

    function handleRemove(file: UploadFile, fileList: UploadFile[]) {
      console.log('handleRemove', file, fileList);
      const { status, uid } = file || {};
      if (status === 'ready') {
        const index = tempFileList.value.findIndex(n => n.uid === uid);
        if (index > -1) {
          tempFileList.value.splice(index, 1);
        }
      }
      if (props.limit == 1) {
        localFileList.value = [];
      } else {
        const index = localFileList.value.findIndex((n: any) => n.uid === uid);
        if (index > -1) {
          localFileList.value.splice(index, 1);
        }
      }
      console.log('tempFileList', tempFileList);
    }

    const { downloadByUrl } = useDownload();
    const previewLoading = ref(false);
    const isDownloadUrl = new Map([]);
    async function handlePreview(file: UploadFile, fileList: any) {
      console.log('handlePreview', file, fileList);
      const fileUrl = file.url || (file?.response as any)?.data.fileUrl;
      if (!fileUrl) return;
      const base64Url = Base64.encode(fileUrl);
      console.log('base64Url', base64Url, fileUrl);
      if (isDownloadUrl.has(base64Url)) {
        createMessage.error('正在下载中，请勿重复点击');
        return;
      }
      isDownloadUrl.set(base64Url, true);
      if (props.previewByApi) {
        previewLoading.value = true;
        await downloadByUrl(fileUrl);
        previewLoading.value = false;
        isDownloadUrl.delete(base64Url);
        return;
      }
      isDownloadUrl.delete(base64Url);
      window.open(fileUrl);
    }
    const itemWidth = computed(() => {
      return props.span > 0 ? `calc(${(100 * props.span) / 24}% - 4px) ` : '100%';
    });
    return {
      localFileList,
      uploadRef,
      uploadLoading,
      headers,
      handleExceed,
      handleBeforeUpload,
      handleSuccess,
      handleError,
      handleChange,
      handleRemove,
      handlePreview,
      tempFileList,
      handleSubmit,
      itemWidth,
      needUploadList,
      previewLoading
    };
  }
});
</script>
<style lang="scss" scoped>
.g-upload {
  :deep(.el-upload) {
    display: unset;
  }
  :deep(.el-upload-list) {
    min-width: 320px;
  }
  :deep(.el-upload-list__item) {
    display: flex;
    align-items: center;
    height: 32px;
  }
  :deep(.el-upload-list__item-name) {
    padding-left: 32px;
    position: relative;
    height: 32px;
    line-height: 32px;
    &:before {
      content: ' ';
      left: 12px;
      top: 8px;
      position: absolute;
      width: 16px;
      height: 16px;
      background-image: url(~@/assets/images/icon_file.png);
      background-size: 16px 16px;
      background-position: center center;
      background-repeat: no-repeat;
    }
  }
  :deep(.el-upload-list__item-status-label) {
    line-height: 34px;
    right: 7px;
  }
  :deep(.el-upload-list__item) {
    background: #f6f9fa;
  }
  :deep(.el-icon--document) {
    display: none;
  }
  :deep(.el-icon--close) {
    top: 16px;
    right: 8px;
  }
  :deep(.el-icon--upload-success) {
    display: none;
  }
}

.inline {
  display: flex;
  position: relative;
  height: 32px;
  .inline--tip {
    position: absolute;
    top: 30px;
  }
  :deep(.el-upload-list) {
    margin: 0;
  }
  :deep(.el-button) {
    margin-right: 8px;
  }
}
.is-row {
  width: 100%;
  &--tip {
    margin-left: 8px;
    display: inline-block;
  }
  :deep(.el-upload-list) {
    width: 100%;
    margin-left: -4px;
    margin-right: -4px;
    display: flex;
    flex-wrap: wrap;
    .el-upload-list__item {
      width: v-bind(itemWidth);
      padding-left: 4px;
      padding-right: 4px;
      margin-left: 4px;
    }
  }
}
</style>
